package com.perceptivesoftware.security;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

/**
 * This implements Perceptive Licensing's "Database Authentication" scheme. It attempts to make a connection to the database using the credentials provided. If the
 * connection succeeds, then user credentials are granted otherwise credentials are rejected. Note: this scheme generally is not used in production but only
 * used in development; generally an enterprise authentication strategy will be employed; this is configured in the spring security configuration file
 * WebContent/WEB-INF/application-security.xml
 * 
 * <p/>
 * Copyright 2012 Perceptive Software
 * <p/>
 * Project: Perceptive Licensing
 * 
 * @author Sean Langford
 * 
 */
public class PerceptiveDbAuthenticationProvider implements AuthenticationProvider {

	private final Logger logger = Logger.getLogger(PerceptiveDbAuthenticationProvider.class);

	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		final String password = (String) authentication.getCredentials();
		final String username = (String) authentication.getPrincipal();

		if (username == null || username.length() == 0 || password == null || password.length() == 0) {
			final String msg = "Invalid username/password";
			logger.info(msg);
			throw new BadCredentialsException(msg);
		}
		else {
			Connection conn = null;
			try {
				final DbAuthenticationDataSource dataSource = new DbAuthenticationDataSource();
				conn = dataSource.getConnection(username, password);

				if (conn == null) {
					final String msg = "Error logging in";
					logger.info(msg);
					throw new BadCredentialsException(msg);
				}
			}
			catch (final SQLException e) {
				final String msg;
				if (e.getMessage().toLowerCase().contains("username") || e.getMessage().toLowerCase().contains("password")
						|| e.getMessage().toLowerCase().contains("credentials")) {
					msg = "Invalid username/password";
				}
				else {
					msg = "Error logging in";
				}
				logger.info(msg + " (" + e.getClass().getName() + ") " + e.getMessage());
				throw new BadCredentialsException(msg);
			}
			catch (final DbAuthenticationConnectionConfigurationException e) {
				logger.error("Failed to Authenticate because " + e.getMessage(), e);
				throw new BadCredentialsException("Could not create database authetication connection.  Contact your system administrator.", e);
			}
			finally {
				try {
					if (conn != null) {
						conn.close();
					}
				}
				catch (final SQLException e) {
					logger.error("Error closing authentication connection");
				}
			}
		}

		final List<GrantedAuthority> gas = new ArrayList<GrantedAuthority>();

		gas.add(new SimpleGrantedAuthority("ROLE_USER"));

		final PerceptiveUserDetails nud = new PerceptiveUserDetails(username);
		return new UsernamePasswordAuthenticationToken(nud, password, gas);
	}

	@Override
	public boolean supports(@SuppressWarnings("unused") Class<?> authentication) {
		return true;
	}

}